using System;
using System.Collections;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.FEZ;
using GHIElectronics.NETMF.Hardware;

namespace Soigeneris
{
    public sealed class TestPort_OnBoard : TestPort
    {
        public TristatePort[] myTri = new TristatePort[8]; // physical digital pins
        //public AnalogIn[] myAPins = new AnalogIn[2];       // physical analog pins

        // A bit of gibberish to make this a singleton class, not thread safe.
        // call the static method Intance to instantiate the class
        private static readonly TestPort_OnBoard instance = new TestPort_OnBoard();
        public static TestPort_OnBoard Instance
        {
            get { return instance; }
        }

        // the one and only constructor
        private TestPort_OnBoard()
        {
            // set up digital pins
            myTri[0] = new TristatePort((Cpu.Pin)FEZ_Pin.Interrupt.UEXT3, false, false, Port.ResistorMode.PullUp);
            myTri[1] = new TristatePort((Cpu.Pin)FEZ_Pin.Interrupt.UEXT5, false, false, Port.ResistorMode.PullUp);
            myTri[2] = new TristatePort((Cpu.Pin)FEZ_Pin.Interrupt.UEXT7, false, false, Port.ResistorMode.PullUp);
            myTri[3] = new TristatePort((Cpu.Pin)FEZ_Pin.Interrupt.UEXT9, false, false, Port.ResistorMode.PullUp);
            myTri[4] = new TristatePort((Cpu.Pin)FEZ_Pin.Interrupt.UEXT4, false, false, Port.ResistorMode.PullUp);
            myTri[5] = new TristatePort((Cpu.Pin)FEZ_Pin.Interrupt.UEXT6, false, false, Port.ResistorMode.PullUp);
            myTri[6] = new TristatePort((Cpu.Pin)FEZ_Pin.Interrupt.UEXT8, false, false, Port.ResistorMode.PullUp);
            myTri[7] = new TristatePort((Cpu.Pin)FEZ_Pin.Interrupt.UEXT10, false, false, Port.ResistorMode.PullUp);

            // set all digital to input
            for (int i = 0; i < 8; i++)
            {
                changeDirection(myTri[i], false);
            }
            
            // set up analog pins
            //myAPins[0] = new AnalogIn((AnalogIn.Pin)FEZ_Pin.AnalogIn.An1);
            //myAPins[1] = new AnalogIn((AnalogIn.Pin)FEZ_Pin.AnalogIn.An2);

            //// initialize the used pin tracker
            //myUsedPins = new PinsUsed();
            //myUsedPins.reset();

            //// initialize the last test tracker
            //myLastTest = new LastTest();
            //myLastTest.dResult = new System.Collections.Queue();
            //myLastTest.aResult = new System.Collections.Queue();
        }

        /// <summary>
        /// Perform digital test.
        /// </summary>
        /// <param name="test"></param>
        /// <returns>False == Test failed, results saved in myLastTest</returns>
        public override bool digitalTest(TestConfig.Test test)
        {
            // build a bit mask of all TestPoints used for this test
            int testMask = 0;
            for (int i = 0; i < test.DrivenPinCount; i++)
            {
                testMask |= (int)(1 << (int)test.GetTestPoint(i) - 1);
            }

            int drive = (int)test.GetDrivePoint() - 1;
            changeDirection(myTri[drive], true); // set drive pin to output
            myTri[drive].Write(false);           // set drive pin low
            Thread.Sleep(1);                     // give pin time to settle

            // Test this pin?
            bool conduction = false;             // should this pin conduct?
            bool testPointState = false;         // reading from pin
            bool failure = false;                // flag set when any failure seen

            int driven = myUsedPins.nextDPin() - 1;
            while (driven >= 0 )
            {
                if (driven != drive)
                {
                    testPointState = !(myTri[driven].Read());
                    conduction = (testMask & (1 << driven)) > 0 ? true : false; // if set this pin should conduct
                    
                    if (conduction ^ testPointState) // true == failure
                    {
                        DigitalResult result = new DigitalResult();
                        result.reading = testPointState;
                        result.drivenPin = driven + 1; // need to report TestPoint NOT DigitalPins
                        result.drivePin = test.DrivePin.Pin;
                        myLastTest.dResult.Enqueue(result);
                        failure = true;
                    }
                }
                driven = myUsedPins.nextDPin() - 1;
            }
            changeDirection(myTri[drive], false); // set driver back to input
            return !failure;
        }


        // helper to prevent exceptions from setting a port to its current state
        // direction true = output, false = input
        public void changeDirection(TristatePort port, bool direction)
        {
            if (port.Active != direction)
            {
                port.Active = direction;
            }
        }
    }
}
